package com.skywolf.chem.match.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alipay.api.AlipayApiException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.ijpay.alipay.AliPayApiConfig;
import com.skywolf.chem.common.config.data.DataSource;
import com.skywolf.chem.common.config.data.DataSourceNames;
import com.skywolf.chem.common.sysconfig.exception.BizException;
import com.skywolf.chem.match.common.constants.MatchConstants;
import com.skywolf.chem.match.common.enums.*;
import com.skywolf.chem.match.entity.*;
import com.skywolf.chem.match.handle.MatchGroupStockHandle;
import com.skywolf.chem.match.handle.OrderPayHandle;
import com.skywolf.chem.match.mapper.OrderMapper;
import com.skywolf.chem.match.params.param.ApplyUserInfoParam;
import com.skywolf.chem.match.params.param.MatchOrderConfirmParam;
import com.skywolf.chem.match.params.vo.*;
import com.skywolf.chem.match.pay.alibaba.config.AliPayProperties;
import com.skywolf.chem.match.pay.wx.config.WxPayProperties;
import com.skywolf.chem.match.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.skywolf.chem.match.utils.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.springframework.transaction.annotation.Transactional;

import static com.skywolf.chem.match.common.config.RabbitMqConfig.DELAYED_EXCHANGE_NAME;
import static com.skywolf.chem.match.common.config.RabbitMqConfig.DELAYED_ROUTING_KEY;

/**
 * <p>
 * 订单详情表 服务实现类
 * </p>
 *
 * @author sj
 * @since 2021-09-24
 */
@Service
@Slf4j
@AllArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    private final RedisUtils redisUtils;

    private final ThreadPoolExecutor threadPoolExecutor;

    private final RedisTemplate<String, Object> redisTemplate;

    private final RedissonClient redissonClient;

    private final IGroupInfoService groupInfoService;

    private final IApplyUserRecordService applyUserRecordService;

    private final RabbitTemplate rabbitTemplate;

    private final IMatchInfoService matchInfoService;

    private final WxPayService wxPayService;

    private final OrderMapper orderMapper;

    private final MatchGroupStockHandle matchGroupStockHandle;

    private final OrderPayHandle orderPayHandle;

    @Override
    @DataSource(DataSourceNames.TWO)
    public OrderConfirmVO confirm(MatchOrderConfirmParam param, Long userId) {
        Order order = this.getOne(new LambdaQueryWrapper<Order>().eq(Order::getMatchUserId, userId).eq(Order::getMatchId, param.getMatchId())
                .eq(Order::getMatchGroupId, param.getMatchGroupId()).notIn(Order::getStatus, MatchOrderStatusEnum.USER_CANCEL.getStatus(), MatchOrderStatusEnum.USER_CANCEL.getStatus()));
        if (null != order) {
            throw new BizException("该活动的当前组您已经报过名！请勿重复报名！");
        }
        OrderConfirmVO orderConfirmVO = new OrderConfirmVO();
        //获取当前用户提交的报名人列表
        String applyUserListKey = MatchConstants.APPLY_USER_INFO_KEY + userId + "-" + param.getMatchId() + "-" + param.getMatchGroupId();
        if (!redisUtils.hasKey(applyUserListKey)) {
            throw new BizException("该订单报名人列表为空，不允许提交！");
        }
        //报名人列表
        Map<String, Object> userInfoMap = redisUtils.hmget(applyUserListKey);
        if (null == userInfoMap || userInfoMap.size() == 0) {
            throw new BizException("该订单报名人列表为空，不允许提交！");
        }
        BeanUtils.copyProperties(param, orderConfirmVO);
        //获取报名人列表
        CompletableFuture<Void> getApplyUserListFuture = CompletableFuture.runAsync(() -> {
            //报名人详细信息列表
            List<ApplyUserInfoParam> infoList = userInfoMap.values().parallelStream().map(item -> (ApplyUserInfoParam) item).collect(Collectors.toList());
            //订单页展示报名人简要信息列表
            List<OrderConfirmVO.OrderApplyUserInfo> orderList = infoList.parallelStream().map(item -> {
                OrderConfirmVO.OrderApplyUserInfo info = new OrderConfirmVO.OrderApplyUserInfo();
                info.setGender(item.getGender());
                //信息脱敏
                info.setUserName(StringUtils.replaceNameX(item.getName()));
                info.setPhone(StringUtils.mobileEncrypt(item.getPhone()));
                info.setIdCard(StringUtils.idEncrypt(item.getIdCard()));
                return info;
            }).collect(Collectors.toList());

            orderConfirmVO.setOrderList(orderList);
            orderConfirmVO.setInfoList(infoList);
        }, threadPoolExecutor);

        // 生成唯一标识，防止订单重复提交
        CompletableFuture<Void> orderTokenCompletableFuture = CompletableFuture.runAsync(() -> {
            String orderToken = generate(BusinessTypeEnum.ORDER);
            orderConfirmVO.setOrderToken(orderToken);
        }, threadPoolExecutor);

        CompletableFuture.allOf(getApplyUserListFuture, orderTokenCompletableFuture).join();
        return orderConfirmVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @DataSource(DataSourceNames.TWO)
    public OrderSubmitVO submit(OrderConfirmVO param, Long userId) {
        // 订单重复提交校验
        String orderToken = param.getOrderToken();
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(MatchConstants.RELEASE_LOCK_LUA_SCRIPT, Long.class);
        Long execute = this.redisTemplate.execute(redisScript, Collections.singletonList(MatchConstants.ORDER_TOKEN_PREFIX + orderToken), orderToken);
        if (execute != null && execute.equals(1L)) {
            throw new BizException("订单不可重复提交!");
        }
        //获取当前用户报名组别详情
        GroupInfo groupInfo = groupInfoService.getById(param.getMatchGroupId());
        //报名人详细信息
        List<ApplyUserInfoParam> infoList = param.getInfoList();
        if (null == infoList || infoList.size() == 0) {
            throw new BizException("该订单报名人列表为空，不允许提交！");
        }
        //报名人总数
        int applyUserCount = infoList.size();
        //计算订单价格(分)
        long applyOrderPrice = groupInfo.getPrice().multiply(new BigDecimal(100)).multiply(new BigDecimal(applyUserCount)).longValue();
        //锁定库存
        boolean result = matchGroupStockHandle.lockStock(applyUserCount, param.getMatchGroupId());
        if (!result) {
            throw new BizException("当前组别报名名额不足！请刷新后再试！");
        }
        //创建待付款订单
        Order order = new Order();
        order.setOrderSn(orderToken)
                .setStatus(MatchOrderStatusEnum.PENDING_PAYMENT.getStatus())
                .setMatchUserId(userId)
                .setEmergencyContactName(param.getEmergencyContactName())
                .setEmergencyContactPhone(param.getEmergencyContactPhone())
                .setMatchId(param.getMatchId())
                .setMatchGroupId(param.getMatchGroupId())
                .setRemark(StringUtils.isNotBlank(param.getRemark()) ? param.getRemark() : "")
                .setTeamName(StringUtils.isNotBlank(param.getTeamName()) ? param.getTeamName() : "")
                .setTotalAmount(applyOrderPrice)
                .setTotalQuantity(applyUserCount);
        this.save(order);
        List<ApplyUserRecord> records = infoList.parallelStream().map(item -> {
            ApplyUserRecord record = new ApplyUserRecord();
            BeanUtils.copyProperties(item, record);
            record.setUuid(orderToken);
            record.setMatchOrderId(order.getId());
            record.setApplyStatus(MatchApplyRecordStatusEnum.UNPAID.getStatus());
            record.setOtherInfoJson(JSONUtil.toJsonStr(item.getTagModels()));
            record.setBirthday(item.getBirthday());
            return record;
        }).collect(Collectors.toList());
        applyUserRecordService.saveBatch(records);
        //将锁定成功的放到redis中
        redisTemplate.opsForValue().set(MatchConstants.LOCKED_STOCK_PREFIX + orderToken, JSONUtil.toJsonStr(records), 5, TimeUnit.MINUTES);
        // 将订单放入延时队列，超时未支付由交换机order.exchange切换到死信队列完成系统自动关单
        rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, orderToken, a -> {
            a.getMessageProperties().setDelay(600000);
            return a;
        });
        //拼接返回参数
        OrderSubmitVO submitVO = new OrderSubmitVO();
        submitVO.setOrderId(order.getId());
        submitVO.setOrderSn(order.getOrderSn());
        //获取活动详情
        Info matchInfo = matchInfoService.getById(param.getMatchId());
        submitVO.setMatchName(matchInfo.getMatchName());
        submitVO.setMatchGroupName(groupInfo.getGroupName());
        submitVO.setPeopleCount(applyUserCount);
        submitVO.setCountPrice(groupInfo.getPrice().multiply(new BigDecimal(applyUserCount)));
        submitVO.setGroupPrice(groupInfo.getPrice());
        submitVO.setVo(param.getOrderList());
        submitVO.setGroupStartTime(LocalDateUtils.format(groupInfo.getMatchStartTime(), LocalDateUtils.DATETIME_PATTERN));
        redisTemplate.opsForValue().set(MatchConstants.ORDER_TOKEN_PREFIX + orderToken, orderToken);
        //删除用户添加的报名记录
        redisUtils.del(MatchConstants.APPLY_USER_INFO_KEY + userId + "-" + order.getMatchId() + "-" + order.getMatchGroupId());
        return submitVO;
    }

    @Override
    @DataSource(DataSourceNames.TWO)
    public OrderPayVo pay(Long userId, Long orderId, MatchPayTypeEnum payTypeEnum, String source) throws Exception {
        //获取支付订单详情
        Order order = this.getById(orderId);
        if (order == null) {
            throw new BizException("订单不存在");
        }
        RLock lock = redissonClient.getLock(MatchConstants.ORDER_SN_PREFIX + order.getOrderSn());
        try {
            lock.tryLock(0L, 5L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
            throw new BizException("订单不可重复支付");
        }
        if (!MatchOrderStatusEnum.PENDING_PAYMENT.getStatus().equals(order.getStatus())) {
            lock.unlock();
            throw new BizException("支付失败，请检查订单状态");

        }

        OrderPayVo orderPayVo = null;
        switch (payTypeEnum) {
            case WX_JSAPI:
                orderPayVo = orderPayHandle.wxJsapiPay(order, lock, source);
                break;
            default:
            case ALIPAY:
                orderPayVo = orderPayHandle.zfbPay(order, lock);
                break;
        }
        if (null == orderPayVo) {
            lock.unlock();
            throw new BizException("调起支付失败");
        }
        return orderPayVo;
    }

    @Override
    public boolean closeOrder(String orderToken, MatchOrderStatusEnum statusEnum) {
        log.info("=======================订单关闭，订单SN：{}=======================", orderToken);
        Order order = this.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderSn, orderToken));
        if (order == null || !MatchOrderStatusEnum.PENDING_PAYMENT.getStatus().equals(order.getStatus())) {
            return false;
        }
        // 如果已经有outTradeNo了就先进行关单
        if (MatchPayTypeEnum.WX_JSAPI.getType().equals(order.getPayType()) && StrUtil.isNotBlank(order.getOutTradeNo())) {
            try {
                wxPayService.closeOrderV3(order.getOutTradeNo());
                order.setOutTradeNo(null);
            } catch (WxPayException e) {
                log.error(e.getMessage(), e);
            }
        }
        if (MatchPayTypeEnum.ALIPAY.getType().equals(order.getPayType())
                && StrUtil.isNotBlank(order.getOutTradeNo())) {
            //支付宝关单
            try {
                // 如果已经有outTradeNo了就先进行关单
                orderPayHandle.aliOrderClose(order);
                order.setOutTradeNo(null);
            } catch (AlipayApiException e) {
                log.error(e.getMessage(), e);
            }

        }
        order.setStatus(statusEnum.getStatus());
        LambdaUpdateWrapper<ApplyUserRecord> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(ApplyUserRecord::getApplyStatus, 106);
        updateWrapper.eq(ApplyUserRecord::getMatchOrderId, order.getId());
        SpringContextHolder.getBean(IApplyUserRecordService.class).update(updateWrapper);
        return this.updateById(order);
    }

    /**
     * @param businessType 业务类型枚举
     * @param digit        业务序号位数
     * @return
     */
    public String generate(BusinessTypeEnum businessType, Integer digit) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        String date = LocalDateTime.now(ZoneOffset.of("+8")).format(formatter);
        String key = MatchConstants.BUSINESS_NO_PREFIX + businessType.getCode() + ":" + date;
        Long increment = redisTemplate.opsForValue().increment(key);
        return date + businessType.getValue() + String.format("%0" + digit + "d", increment);
    }


    public String generate(BusinessTypeEnum businessType) {
        Integer defaultDigit = 6;
        return generate(businessType, defaultDigit);
    }


    @Override
    @DataSource(DataSourceNames.TWO)
    public void cancelOrder(Long orderId) {
        Order order = getById(orderId);
        if (null == order) {
            throw new BizException("订单不存在");
        }
        if (closeOrder(order.getOrderSn(), MatchOrderStatusEnum.USER_CANCEL)) {
            matchGroupStockHandle.unlockStock(order.getOrderSn());
        } else {
            throw new BizException("取消订单失败！");
        }
    }


    @Override
    @DataSource(DataSourceNames.TWO)
    public List<OrderListQueryVo> getOrderListByStatus(Integer status, Long userId) {
        //查询用户关注的活动列表
        if (201 == status) {
            return matchInfoService.getCollectQueryVoByUserId(userId);
        }
        List<OrderListQueryVo> queryVos = orderMapper.getOrderListByStatus(status, userId);
        if (null == queryVos || queryVos.size() == 0) {
            return new ArrayList<>();
        }
        return queryVos.parallelStream().map(item -> {
            if (null != item.getCountPrice()) {
                //将 价格(分）  ->>  价格(元)
                item.setCountPrice(item.getCountPrice().divide(new BigDecimal(100), 2, BigDecimal.ROUND_DOWN));
            }
            return item;
        }).collect(Collectors.toList());
    }

    @Override
    @DataSource(DataSourceNames.TWO)
    public OrderInfoQueryVo orderInfoQueryVo(Long id) throws Exception {
        OrderInfoQueryVo queryVo = orderMapper.orderInfoQueryVo(id);
        String passTime = LocalDateUtils.format(LocalDateUtils.plus(queryVo.getCreateTime(), 10, ChronoUnit.MINUTES), LocalDateUtils.DATETIME_PATTERN);
        queryVo.setPassTime(passTime);
        if (null == queryVo) {
            throw new BizException("当前订单不存在!");
        }
        //价格转换
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            //将 价格(分）  ->>  价格(元)
            if (null != queryVo.getPayCount()) {
                queryVo.setPayAmount(new BigDecimal(queryVo.getPayCount()).divide(new BigDecimal(100), 2, BigDecimal.ROUND_DOWN));
            }
            if (null != queryVo.getTotalCount()) {
                queryVo.setTotalAmount(new BigDecimal(queryVo.getTotalCount()).divide(new BigDecimal(100), 2, BigDecimal.ROUND_DOWN));
            }
        }, threadPoolExecutor);
        List<ApplyUserRecord> applyUserRecords =
                applyUserRecordService.list(new LambdaQueryWrapper<ApplyUserRecord>()
                        .eq(ApplyUserRecord::getMatchOrderId, id));
        if (null == applyUserRecords || applyUserRecords.size() == 0) {
            return queryVo;
        }
        //计算退款金额
        CompletableFuture<Void> refundPriceFuture = CompletableFuture.runAsync(() -> {
            //计算退款金额
            BigDecimal refundPrice = applyUserRecords.parallelStream()
                    .filter(item -> item.getApplyStatus().equals(MatchApplyRecordStatusEnum.APPLY_REFUND.getStatus()))
                    .map(item -> queryVo.getGroupPrice())
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            queryVo.setRefundPrice(refundPrice);
        }, threadPoolExecutor);
        //获取报名人列表
        CompletableFuture<Void> applyUserInfo = CompletableFuture.runAsync(() -> {
            List<OrderInfoQueryVo.ApplyUserInfoQueryVo> applyUserInfoQueryVos = applyUserRecords.parallelStream().map(item -> {
                OrderInfoQueryVo.ApplyUserInfoQueryVo vo = new OrderInfoQueryVo.ApplyUserInfoQueryVo();
                vo.setId(item.getId());
                vo.setGender(item.getGender());
                //数据脱敏
                vo.setUserName(StringUtils.replaceNameX(item.getName()));
                vo.setPhone(StringUtils.mobileEncrypt(item.getPhone()));
                vo.setIdCard(StringUtils.idEncrypt(item.getIdCard()));
                vo.setStatus(item.getApplyStatus());
                return vo;
            }).collect(Collectors.toList());
            queryVo.setApplyUserInfoQueryVos(applyUserInfoQueryVos);
        }, threadPoolExecutor);
        CompletableFuture.allOf(priceFuture, refundPriceFuture, applyUserInfo).join();
        List<Sponsor> sponsorList = SpringContextHolder.getBean(ISponsorService.class)
                .list(new LambdaQueryWrapper<Sponsor>().eq(Sponsor::getMatchId, queryVo.getMatchId()));
        if (null != sponsorList && sponsorList.size() != 0) {
            queryVo.setMobileList(sponsorList);
        } else {
            queryVo.setMobileList(new ArrayList<>());
        }
        return queryVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @DataSource(DataSourceNames.TWO)
    public void cancelApply(Long id) {
        ApplyUserRecord applyUserRecord = applyUserRecordService.getById(id);
        if (applyUserRecord.getApplyStatus().equals(MatchApplyRecordStatusEnum.CANCEL_APPLY_AUDIT.getStatus()) ||
                applyUserRecord.getApplyStatus().equals(MatchApplyRecordStatusEnum.UNPAID.getStatus()) ||
                applyUserRecord.getApplyStatus().equals(MatchApplyRecordStatusEnum.APPLY_REFUND.getStatus())) {
            throw new BizException("当前用户不满足退款条件");
        }
        LambdaUpdateWrapper<ApplyUserRecord> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(ApplyUserRecord::getApplyStatus, MatchApplyRecordStatusEnum.CANCEL_APPLY_AUDIT.getStatus());
        updateWrapper.eq(ApplyUserRecord::getId, id);
        boolean result = applyUserRecordService.update(updateWrapper);
        if (!result) {
            throw new BizException("申请退款失败！");
        }
    }
}
